package org.example;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import okhttp3.*;

import java.io.*;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

public class Upload123Pan {
    private static String apiUrl = "https://open-api.123pan.com";
    private static String clientId = "";
    private static String clientSecret = "";
    private static CacheManager cacheManager = new CacheManager("cache");
    private static String accessToken;
    private static ObjectMapper mapper = new ObjectMapper();
    private static OkHttpClient client = new OkHttpClient();

    private static final String accessTokenKey = "pan123_access_token";
    private static final String parentFileIDKey = "pan123_file_parent_id";

    public static String getAccessToken() throws IOException {
        if (accessToken != null && !accessToken.isEmpty()) {
            return accessToken;
        }

        String redisToken = getFromRedis(accessTokenKey);
        if (redisToken != null && !redisToken.isEmpty()) {
            accessToken = redisToken;
            return redisToken;
        }

        String url = apiUrl + "/api/v1/access_token";
        OkHttpClient client = new OkHttpClient();

        ObjectMapper mapper = new ObjectMapper();
        ObjectNode data = mapper.createObjectNode();
        data.put("clientID", clientId);
        data.put("clientSecret", clientSecret);

        RequestBody body = RequestBody.create(data.toString(), MediaType.parse("application/json; charset=utf-8"));
        Request request = new Request.Builder()
                .url(url)
                .header("platform","open_platform")
                .post(body)
                .build();

        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) {
            throw new IOException("Unexpected code " + response);
        }

        JsonNode result = mapper.readTree(response.body().string());
        if (result.get("code").asInt() != 0) {
            throw new IOException(result.get("message").asText());
        }

        JsonNode resultData = result.get("data");
        accessToken = resultData.get("accessToken").asText();
        int expiredAt = (int) (resultData.get("expiredAt").asLong() - System.currentTimeMillis() / 1000);

        saveToRedis(accessTokenKey, accessToken, expiredAt);

        return accessToken;
    }

    public static Map<String, Object> upload(Map<String, Object> fileData) throws IOException, NoSuchAlgorithmException {
        String filePath = (String) fileData.get("full_path");
        String fileName = (String) fileData.get("name");
        long fileSize = (Long) fileData.get("size");
        String fileId = (String) fileData.get("file_id");
        String firAppId = (String) fileData.get("fir_app_id");



        String md5 = calculateMD5(filePath);
        File file = new File(filePath);
        String dirId = getDistributeDirId();

        // Step 1: Create file
        JsonNode createFileResponse = createFile(fileName, dirId, fileSize, md5);

        String preuploadID = null;
        if (!createFileResponse.get("reuse").asBoolean()) {
            preuploadID = createFileResponse.get("preuploadID").asText();
            long sliceSize = createFileResponse.get("sliceSize").asLong();

            int totalSlices = (int) Math.ceil((double) fileSize / sliceSize);

            try (FileInputStream fis = new FileInputStream(file)) {
                for (int sliceNo = 1; sliceNo <= totalSlices; sliceNo++) {
                    String uploadUrl = getUploadUrl(preuploadID, sliceNo, sliceSize);

                    long start = (sliceNo - 1) * sliceSize;
                    long end = Math.min(sliceNo * sliceSize, fileSize);
                    try (InputStream sliceStream = new FileInputStream(file)) {
                        sliceStream.skip(start);
                        byte[] buffer = new byte[(int) (end - start)];
                        sliceStream.read(buffer);

                        uploadFile(uploadUrl, buffer);
                    }
                }

                if (fileSize > sliceSize) {
                    listUploadParts(preuploadID);
                }

                JsonNode uploadCompleteResponse = uploadComplete(preuploadID);
                if (uploadCompleteResponse.get("async").asBoolean()) {
                    // 处理异步上传结果
                    Map<String, String> asyncData = new HashMap<>();
                    asyncData.put("file_id", fileId);
                    asyncData.put("fir_app_id", firAppId);
                    asyncData.put("preuploadID", preuploadID);
                    // TODO: Implement async handling
                } else if (uploadCompleteResponse.get("completed").asBoolean()) {
                    String fileID = uploadCompleteResponse.get("fileID").asText();
                    JsonNode directLinkResponse = getDirectLink(fileID);
                    String url = directLinkResponse.get("url").asText();
                    Map<String, Object> result = new HashMap<>();
                    result.put("code", 0);
                    result.put("url", url);
                    result.put("fileID", fileID);
                    result.put("preuploadID", preuploadID);
                    return result;
                }
            }
        } else {
            String fileID = createFileResponse.get("fileID").asText();
            JsonNode directLinkResponse = getDirectLink(fileID);
            String url = directLinkResponse.get("url").asText();
            Map<String, Object> result = new HashMap<>();
            result.put("code", 0);
            result.put("url", url);
            result.put("fileID", fileID);
            result.put("preuploadID", preuploadID);
            return result;
        }

        throw new IOException("Upload failed");
    }

    public static String calculateMD5(String filePath) throws NoSuchAlgorithmException, IOException {
        MessageDigest md = MessageDigest.getInstance("MD5");
        try (FileInputStream fis = new FileInputStream(new File(filePath))) {
            byte[] dataBytes = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(dataBytes)) != -1) {
                md.update(dataBytes, 0, bytesRead);
            }
        }

        byte[] mdBytes = md.digest();
        BigInteger bigInt = new BigInteger(1, mdBytes);
        return bigInt.toString(16);
    }

    private static JsonNode createFile(String filename, String parentFileId, long size, String etag) throws IOException {
        String url = apiUrl + "/upload/v1/file/create";
        Map<String, Object> data = new HashMap<>();
        data.put("filename", filename);
        data.put("parentFileID", parentFileId);
        data.put("size", size);
        data.put("etag", etag);

        RequestBody body = RequestBody.create(mapper.writeValueAsString(data), MediaType.get("application/json; charset=utf-8"));
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .header("Authorization", "Bearer " + getAccessToken())
                .addHeader("platform","open_platform")
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response);
            }

            JsonNode result = mapper.readTree(response.body().string());
            if (result.get("code").asInt() != 0) {
                throw new IOException(result.get("message").asText());
            }

            return result.get("data");
        }
    }

    public static String getDistributeDirId() throws IOException {
        String dirId = getFromRedis(parentFileIDKey);
        if (dirId != null) {
            return dirId;
        }

        String url = apiUrl + "/api/v2/file/list";
        Map<String, Object> data = new HashMap<>();
        data.put("parentFileId", 0);
        data.put("limit", 100);

        Request request = new Request.Builder()
                .url(url + "?" + createQueryString(data))
                .header("Authorization", "Bearer " + getAccessToken())
                .addHeader("platform","open_platform")
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response);
            }

            JsonNode result = mapper.readTree(response.body().string());
            if (result.get("code").asInt() != 0) {
                throw new IOException(result.get("message").asText());
            }

            JsonNode fileList = result.get("data").get("fileList");

            for (JsonNode file : fileList) {
                if (file.get("category").asInt() == 0 &&
                        file.get("parentFileId").asInt() == 0 &&
                        "distribute".equals(file.get("filename").asText())) {
                    dirId = file.get("fileId").asText();
                    cacheManager.saveToCache("distribute_dir_id", dirId, 3600); // Cache for 1 hour
                    return dirId;
                }
            }

            // If "distribute" folder doesn't exist, create it
            dirId = createFolder("distribute");
            saveToRedis(parentFileIDKey, dirId, 3600); // Cache for 1 hour
            return dirId;
        }
    }

    private static String createFolder(String folderName) throws IOException {
        String url = apiUrl + "/upload/v1/file/mkdir";
        Map<String, Object> data = new HashMap<>();
        data.put("parentID", 0);
        data.put("name", folderName);

        RequestBody body = RequestBody.create(mapper.writeValueAsString(data), MediaType.get("application/json; charset=utf-8"));
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .header("Authorization", "Bearer " + getAccessToken())
                .addHeader("platform","open_platform")
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response);
            }

            JsonNode result = mapper.readTree(response.body().string());
            if (result.get("code").asInt() != 0) {
                throw new IOException(result.get("message").asText());
            }

            String dirId = result.get("data").get("dirID").asText();
            enableDirectLink(dirId);
            return dirId;
        }
    }

    private static void enableDirectLink(String fileId) throws IOException {
        String url = apiUrl + "/api/v1/direct-link/enable";
        Map<String, Object> data = new HashMap<>();
        data.put("fileID", fileId);

        RequestBody body = RequestBody.create(mapper.writeValueAsString(data), MediaType.get("application/json; charset=utf-8"));
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .header("Authorization", "Bearer " + getAccessToken())
                .addHeader("platform","open_platform")
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response);
            }

            JsonNode result = mapper.readTree(response.body().string());
            if (result.get("code").asInt() != 0) {
                throw new IOException(result.get("message").asText());
            }
        }
    }

    private static String createQueryString(Map<String, Object> params) {
        StringBuilder queryString = new StringBuilder();
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            queryString.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        return queryString.substring(0, queryString.length() - 1);
    }

    private static String getUploadUrl(String preuploadID, int sliceNo, long sliceSize) throws IOException {
        String url = apiUrl + "/upload/v1/file/get_upload_url";
        OkHttpClient client = new OkHttpClient();

        ObjectMapper mapper = new ObjectMapper();
        ObjectNode data = mapper.createObjectNode();
        data.put("preuploadID", preuploadID);
        data.put("sliceNo", sliceNo);

        RequestBody body = RequestBody.create(data.toString(), MediaType.parse("application/json; charset=utf-8"));
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .addHeader("Authorization", "Bearer " + getAccessToken())
                .build();

        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) {
            throw new IOException("Unexpected code " + response);
        }

        JsonNode result = mapper.readTree(response.body().string());
        if (result.get("code").asInt() != 0) {
            throw new IOException(result.get("message").asText());
        }

        JsonNode resultData = result.get("data");
        return resultData.get("presignedURL").asText();
    }

    private static void uploadFile(String uploadUrl, byte[] data) throws IOException {
        RequestBody body = RequestBody.create(data, MediaType.get("application/octet-stream"));
        Request request = new Request.Builder()
                .url(uploadUrl)
                .put(body)
                .header("Authorization", "Bearer " + getAccessToken())
                .addHeader("platform","open_platform")
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response);
            }

            JsonNode result = mapper.readTree(response.body().string());
            if (result.get("code").asInt() != 0) {
                throw new IOException(result.get("message").asText());
            }
        }
    }

    public static JsonNode listUploadParts(String fileId) throws IOException {
        String url = apiUrl + "/upload/v1/file/upload/parts";
        Map<String, Object> data = new HashMap<>();
        data.put("fileID", fileId);

        RequestBody body = RequestBody.create(mapper.writeValueAsString(data), MediaType.get("application/json; charset=utf-8"));
        Request request = new Request.Builder()
                .url(url)
                .post(body)

                .header("Authorization", "Bearer " + getAccessToken())
                .addHeader("platform","open_platform")
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (!response.isSuccessful()) {
                throw new IOException("Unexpected code " + response);
            }

            JsonNode result = mapper.readTree(response.body().string());
            if (result.get("code").asInt() != 0) {
                throw new IOException(result.get("message").asText());
            }

            return result.get("data").get("parts");
        }
    }

    private static JsonNode uploadComplete(String preuploadID) throws IOException {
        String url = apiUrl + "/upload/v1/file/upload_complete";
        OkHttpClient client = new OkHttpClient();

        ObjectMapper mapper = new ObjectMapper();
        ObjectNode data = mapper.createObjectNode();
        data.put("preuploadID", preuploadID);

        RequestBody body = RequestBody.create(data.toString(), MediaType.parse("application/json; charset=utf-8"));
        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .addHeader("Authorization", "Bearer " + getAccessToken())
                .addHeader("platform","open_platform")
                .build();

        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) {
            throw new IOException("Unexpected code " + response);
        }

        JsonNode result = mapper.readTree(response.body().string());
        if (result.get("code").asInt() != 0) {
            throw new IOException(result.get("message").asText());
        }

        JsonNode resultData = result.get("data");
        return resultData;
    }

    private static JsonNode getDirectLink(String fileID) throws IOException {
        String url = apiUrl + "/api/v1/direct-link/url";
        OkHttpClient client = new OkHttpClient();

        Map<String, Object> data = new HashMap<>();
        data.put("fileID", fileID);

        Request request = new Request.Builder()
                .url(url + "?" + createQueryString(data))
                .addHeader("Authorization", "Bearer " + getAccessToken())
                .addHeader("platform","open_platform")
                .build();

        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) {
            throw new IOException("Unexpected code " + response);
        }

        JsonNode result = mapper.readTree(response.body().string());
        if (result.get("code").asInt() != 0) {
            throw new IOException(result.get("message").asText());
        }

        JsonNode resultData = result.get("data");
        return resultData;
    }

    // Placeholder methods for Redis operations
    private static String getFromRedis(String key) throws IOException {
        // Implement Redis get operation
        if(cacheManager == null){
            cacheManager = new CacheManager("cache");
        }
        return cacheManager.getFromCache(key);
    }

    private static String saveToRedis(String key, String value, int expiresIn) throws IOException {
        if(cacheManager == null){
            cacheManager = new CacheManager("cache");
        }
        cacheManager.saveToCache("access_token", accessToken, expiresIn );
        return value;
    }
}
